/*
 * DCF77.c
 *
 *  Created on: 22.09.2011
 *      Author: windows97
 */

#include <avr/io.h>
#include <avr/interrupt.h>
#include <HwAbstraction.h>
#include "TimeDate.h"
#include "TimerControl.h"
#include "HwAbstraction.h"
#include "Globals.h"
#include "DisplayControl.h" /* for debug purposes */
#include "DCF77.h"

static volatile unsigned long long gDcfSignal_t = 0;
static const T_DCF77* gDcfSignal_pt = (T_DCF77*) &gDcfSignal_t;
static volatile T_DCF77Flags gDcfFlags_t;
static uint8_t bcd_to_hex(uint8_t bcd);
static uint8_t gEstimatedMinute_u8 = 0; /* to store the last minute */

static uint8_t DcfSignalInterval_u8 = 0;
static uint8_t DcfSignalLastInterval_u8 = 0;

#define DCF_ONE_SECOND_U8 ((uint8_t) (50U))

static uint8_t DcfSecondCounter_u8 = 0;

void DcfHeartbeat( void );

void DcfInit( void )
{
  TicRegisterNotification( DcfHeartbeat );
  EIMSK |= (1 << INT1);
  INT_CONTROL |= DCF77_INT_FALLING_EDGE;
}

uint8_t DcfGetSyncState( void )
{
  return (gDcfFlags_t.Sync);
}

void DcfGetSignal( T_DCF77 *iDcfSignal_pt )
{
  *iDcfSignal_pt = *gDcfSignal_pt;
}

ISR(DCF77_INT)
{
  static uint8_t pause = 0;
  static uint8_t rx_counter = 0;
  /* check if its a rising edge -> start of pulse */

  if ((INT_CONTROL == ((INT_CONTROL & ~(DCF77_INT_CLEARFLAGS)) | DCF77_INT_FALLING_EDGE )))/* -> falling edge */
    {
      INT_CONTROL = ((INT_CONTROL & ~(DCF77_INT_CLEARFLAGS)) | DCF77_INT_RISING_EDGE );
    /* Save pause and measure interval */
    pause = DcfSignalInterval_u8;
    DcfSignalInterval_u8 = 0;
    if (pause > 80 && pause < 120)
    {
      /* a new minute starts */
      /* Synchronisation pause */
      rx_counter = 0;
      /* reset dcf signal */
      gDcfSignal_t = 0;
      TicSyncCounter();
      TidUpdateSecond();
      DcfSecondCounter_u8 = 0;
    }
  }
  else if (INT_CONTROL == ((INT_CONTROL & ~(DCF77_INT_CLEARFLAGS)) | DCF77_INT_RISING_EDGE ))
  {
    INT_CONTROL = ((INT_CONTROL & ~(DCF77_INT_CLEARFLAGS)) | DCF77_INT_FALLING_EDGE );
    // Save interval length and measure pause
    if (DcfSignalInterval_u8 > 2)
    {
      DcfSignalLastInterval_u8 = DcfSignalInterval_u8;
      DcfSignalInterval_u8 = 0;

      if (rx_counter == 21 || rx_counter == 29 || rx_counter == 36)
      {
        gDcfFlags_t.PartityError = 0;
      }
      //Save P1
      if (rx_counter == 28)
      {
        gDcfFlags_t.Partity1 = gDcfFlags_t.PartityError;
      }
      //Save P2
      if (rx_counter == 35)
      {
        gDcfFlags_t.Partity2 = gDcfFlags_t.PartityError;
      }
      //Save P3
      if (rx_counter == 58)
      {
        gDcfFlags_t.Partity3 = gDcfFlags_t.PartityError;
      }

      if (DcfSignalLastInterval_u8 > DCF77_PULSEHIGH_LENGTH)
      {
        gDcfSignal_t = gDcfSignal_t | ((unsigned long long) 1 << rx_counter);
        //Toggle helps parity
        gDcfFlags_t.PartityError ^= 1;
      }
      if (rx_counter == 58)
      {
        if( ( gDcfFlags_t.Partity1 == gDcfSignal_pt->P1 ) &&
            ( gDcfFlags_t.Partity2 == gDcfSignal_pt->P2 ) &&
            ( gDcfFlags_t.Partity3 == gDcfSignal_pt->P3 ) )
        {
          T_TimeDate lTimeDate;
          lTimeDate.Time.Second   = 0;
          lTimeDate.Time.Minute   = bcd_to_hex(gDcfSignal_pt->Min);
          lTimeDate.Time.Hour     = bcd_to_hex(gDcfSignal_pt->Hour);
          lTimeDate.Date.Day      = bcd_to_hex(gDcfSignal_pt->Day);
          lTimeDate.Date.Month    = bcd_to_hex(gDcfSignal_pt->Month);
          lTimeDate.Date.WeekDay  = gDcfSignal_pt->Weekday;
          lTimeDate.Date.Year     = bcd_to_hex(gDcfSignal_pt->Year);

          if (( lTimeDate.Time.Minute == gEstimatedMinute_u8 ) &&
              ( lTimeDate.Date.Month  < DATE_MON_MAX    ) &&
              ( lTimeDate.Time.Minute < TIME_MIN_MAX    ) &&
              ( lTimeDate.Time.Hour   < TIME_HOUR_MAX   ) &&
              ( lTimeDate.Date.Day    < DATE_DAY_MAX    ) )
          {
#ifdef DEBUG0
            static uint8_t lLastSync_u8 = 0;
            if( lLastSync_u8 == 0 )
            {
              lLastSync_u8 = 1;
              UART_PUT_STR( "FirstSync! ");
            }
#endif
            TidSetTimeDate( &lTimeDate );
            gDcfFlags_t.Sync = 1;
          }
          else
          {
            /* no sync assumed */
            gDcfFlags_t.Sync = 0;
          }
          /* estimate next minute to compare it on the next run */
          if( lTimeDate.Time.Minute < TIME_MIN_MAX - 1)
          {
            gEstimatedMinute_u8 = lTimeDate.Time.Minute + 1;
          }
          else /* 59 sec */
          {
            gEstimatedMinute_u8 = 0;
          }
        }
        else
        {
          /* parity doesn't match -> no sync */
          gDcfFlags_t.Sync = 0;
        }
      }
      rx_counter++;
    }
  }
}

uint8_t bcd_to_hex(uint8_t bcd)
{
  return (bcd - ((bcd / 16) * 6));
}

void DcfHeartbeat( void )
{
  ++DcfSignalInterval_u8;
  if( ++DcfSecondCounter_u8 == DCF_ONE_SECOND_U8 )
  {
    TidUpdateSecond();
    DcfSecondCounter_u8 = 0;
  }
}
